博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用Selenium WebDriver测试Flask应用程序-第1部分
阅读量:2509 次
发布时间:2019-05-11

本文共 49738 字,大约阅读时间需要 165 分钟。

Ever wondered how to write tests for the front-end of your web application? You may already have functional back-end tests, for example to ensure that your models and views are working. However, you may be unsure how to simulate a user of your app for testing. How can you test front-end functions like registration and logging in, which are done in a browser?

是否曾经想过如何为Web应用程序的前端编写测试? 您可能已经具有功能性的后端测试,例如,以确保您的模型和视图正常工作。 但是,您可能不确定如何模拟应用程序的用户进行测试。 您如何测试在浏览器中完成的前端功能,例如注册和登录?

In this two-part tutorial, I will show you how to write front-end tests for an existing Python/Flask web application. You should therefore already have a functional application, along with a virtual environment with the necessary software dependencies installed. We will use Project Dream Team, a CRUD web app I built in a three-part tutorial (here is , and ). I recommend that you go through the tutorial and build out the app, especially if you are new to Flask or to testing in Flask. If you have built and tested a Flask app before, or don't want to go through the tutorial, you can clone the complete app from my GitHub repository, and follow the set-up instructions in the README file.

在这个分为两部分的教程中,我将向您展示如何为现有的Python / Flask Web应用程序编写前端测试。 因此,您应该已经具有一个正常运行的应用程序,以及一个已安装必要软件依赖项的虚拟环境。 我们将使用Project Dream Team,这是我在一个分为三部分的教程(这里是 , 和 )中构建的CRUD Web应用程序。 我建议您阅读本教程并构建应用程序,特别是如果您不熟悉Flask或在Flask中进行测试。 如果您之前已经构建并测试过Flask应用程序,或者不想阅读本教程,则可以从GitHub存储库中克隆完整的应用程序, 并按照README文件中的设置说明进行操作。

( )

To recap, the application we will be testing is an employee management app. The app allows users to register as employees and login. It also allows admin users to view all employees, as well as to create departments and roles, and then assign them to each employee.

回顾一下,我们将要测试的应用程序是员工管理应用程序。 该应用程序允许用户注册为员工并登录。 它还允许管理员用户查看所有员工,以及创建部门和角色,然后将其分配给每个员工。

We will therefore write tests to ensure all these functions are achieved in the app. If you followed the tutorial, you should already have a test database set up. If not, you can create one and grant your database user privileges on it, as follows:

因此,我们将编写测试以确保在应用程序中实现所有这些功能。 如果按照本教程进行操作,则应该已经建立了测试数据库。 如果没有,则可以创建一个,并为其授予数据库用户特权,如下所示:

$ mysql -u rootmysql> CREATE DATABASE dreamteam_test;Query OK, 1 row affected (0.00 sec)mysql> GRANT ALL PRIVILEGES ON dreamteam_test . * TO 'dt_admin'@'localhost';Query OK, 0 rows affected (0.00 sec)

We use the test database to run our tests to prevent our test data from being saved into the main database we are using for development.

我们使用测试数据库来运行测试,以防止将测试数据保存到用于开发的主数据库中。

The back-end tests, which were already written in the app tutorial, test that the app's models and views are working as expected. The front-end tests, which we will write in this tutorial, will simulate a user using the app in the browser. For these tests, we will create test data, and then test the registration, login, and CRUD functionality of the app.

应用程序教程中已经编写了后端测试,用于测试应用程序的模型和视图是否按预期工作。 我们将在本教程中编写的前端测试将使用浏览器中的应用程序模拟用户。 对于这些测试,我们将创建测试数据,然后测试该应用程序的注册,登录和CRUD功能。

( )

We previously had only one test file, tests.py, in the root directory. Rather than have all the tests in one file, we shall create a tests directory. In it, create two new files: __init__.py and test_front_end.py. Move the tests.py file to the tests directory and rename it to test_back_end.py. Your tests directory should now look like this:

我们以前在根目录中只有一个测试文件tests.py 。 与其将所有测试放在一个文件中,不如创建一个tests目录。 在其中创建两个新文件: __init__.py test_front_end.pytest_front_end.py 。 将tests.py文件移动到tests目录,并将其重命名为test_back_end.py 。 现在,您的tests目录应如下所示:

└── tests    ├── __init__.py    ├── test_back_end.py    └── test_front_end.py

The __init__.py file initializes the tests directory as a Python package. test-back_end.py will contain all our back-end tests, while test_front_end.py will contain all our front-end tests. Update the test_back_end.py file as follows:

__init__.py文件将tests目录初始化为Python包。 test-back_end.py将包含我们所有的后端测试,而test_front_end.py将包含我们所有的前端测试。 如下更新test_back_end.py文件:

# tests/test_back_end.pyimport unittestfrom flask import abort, url_forfrom flask_testing import TestCasefrom app import create_app, dbfrom app.models import Department, Employee, Roleclass TestBase(TestCase):    def create_app(self):        # pass in test configurations        config_name = 'testing'        app = create_app(config_name)        app.config.update(            SQLALCHEMY_DATABASE_URI='mysql://dt_admin:dt2016@localhost/dreamteam_test'        )        return app    def setUp(self):        """        Will be called before every test        """        db.session.commit()        db.drop_all()        db.create_all()        # create test admin user        admin = Employee(username="admin", password="admin2016", is_admin=True)        # create test non-admin user        employee = Employee(username="test_user", password="test2016")        # save users to database        db.session.add(admin)        db.session.add(employee)        db.session.commit()    def tearDown(self):        """        Will be called after every test        """        db.session.remove()        db.drop_all()class TestModels(TestBase):    def test_employee_model(self):        """        Test number of records in Employee table        """        self.assertEqual(Employee.query.count(), 2)    def test_department_model(self):        """        Test number of records in Department table        """        # create test department        department = Department(name="IT", description="The IT Department")        # save department to database        db.session.add(department)        db.session.commit()        self.assertEqual(Department.query.count(), 1)    def test_role_model(self):        """        Test number of records in Role table        """        # create test role        role = Role(name="CEO", description="Run the whole company")        # save role to database        db.session.add(role)        db.session.commit()        self.assertEqual(Role.query.count(), 1)class TestViews(TestBase):    def test_homepage_view(self):        """        Test that homepage is accessible without login        """        response = self.client.get(url_for('home.homepage'))        self.assertEqual(response.status_code, 200)    def test_login_view(self):        """        Test that login page is accessible without login        """        response = self.client.get(url_for('auth.login'))        self.assertEqual(response.status_code, 200)    def test_logout_view(self):        """        Test that logout link is inaccessible without login        and redirects to login page then to logout        """        target_url = url_for('auth.logout')        redirect_url = url_for('auth.login', next=target_url)        response = self.client.get(target_url)        self.assertEqual(response.status_code, 302)        self.assertRedirects(response, redirect_url)    def test_dashboard_view(self):        """        Test that dashboard is inaccessible without login        and redirects to login page then to dashboard        """        target_url = url_for('home.dashboard')        redirect_url = url_for('auth.login', next=target_url)        response = self.client.get(target_url)        self.assertEqual(response.status_code, 302)        self.assertRedirects(response, redirect_url)    def test_admin_dashboard_view(self):        """        Test that dashboard is inaccessible without login        and redirects to login page then to dashboard        """        target_url = url_for('home.admin_dashboard')        redirect_url = url_for('auth.login', next=target_url)        response = self.client.get(target_url)        self.assertEqual(response.status_code, 302)        self.assertRedirects(response, redirect_url)    def test_departments_view(self):        """        Test that departments page is inaccessible without login        and redirects to login page then to departments page        """        target_url = url_for('admin.list_departments')        redirect_url = url_for('auth.login', next=target_url)        response = self.client.get(target_url)        self.assertEqual(response.status_code, 302)        self.assertRedirects(response, redirect_url)    def test_roles_view(self):        """        Test that roles page is inaccessible without login        and redirects to login page then to roles page        """        target_url = url_for('admin.list_roles')        redirect_url = url_for('auth.login', next=target_url)        response = self.client.get(target_url)        self.assertEqual(response.status_code, 302)        self.assertRedirects(response, redirect_url)    def test_employees_view(self):        """        Test that employees page is inaccessible without login        and redirects to login page then to employees page        """        target_url = url_for('admin.list_employees')        redirect_url = url_for('auth.login', next=target_url)        response = self.client.get(target_url)        self.assertEqual(response.status_code, 302)        self.assertRedirects(response, redirect_url)class TestErrorPages(TestBase):    def test_403_forbidden(self):        # create route to abort the request with the 403 Error        @self.app.route('/403')        def forbidden_error():            abort(403)        response = self.client.get('/403')        self.assertEqual(response.status_code, 403)        self.assertTrue("403 Error" in response.data)    def test_404_not_found(self):        response = self.client.get('/nothinghere')        self.assertEqual(response.status_code, 404)        self.assertTrue("404 Error" in response.data)    def test_500_internal_server_error(self):        # create route to abort the request with the 500 Error        @self.app.route('/500')        def internal_server_error():            abort(500)        response = self.client.get('/500')        self.assertEqual(response.status_code, 500)        self.assertTrue("500 Error" in response.data)if __name__ == '__main__':    unittest.main()

Because we now have multiple test files, we need a way to run all the tests at once. It would be inconvenient to have to run each test file individually. For this we will use , a package that extends Python's unit testing framework, unittest, and makes testing easier.

因为我们现在有多个测试文件,所以我们需要一种可以一次运行所有测试的方法。 必须单独运行每个测试文件会很不方便。 为此,我们将使用软件包,该软件包扩展了Python的单元测试框架unittest ,并使测试更加容易。

$ pipinstall nose2

Now, we will run the tests using the nose2 command. This command looks for all files whose names begin with "test", and runs the methods inside these files whose names begin with "test". This means that if you name your test files and methods incorrectly, they will not be run by nose2. First, remember to ensure that your MySQL server is running. You can do this by running the following command:

现在,我们将使用nose2命令运行测试。 该命令查找名称以“ test”开头的所有文件,并在名称以“ test”开头的文件中运行方法。 这意味着,如果您错误地命名测试文件和方法,它们将不会由nas2运行。 首先,请记住确保您MySQL服务器正在运行。 您可以通过运行以下命令来执行此操作:

$ mysqld

Then, in another terminal window, run the tests:

然后,在另一个终端窗口中,运行测试:

$ nose2..............----------------------------------------------------------------------Ran 14 tests in 2.582sOK

( )

is a suite of tools to automate web browsers for a variety of purposes. in particular is useful when writing browser-based tests.

是用于自动实现各种目的的Web浏览器的工具套件。 当编写基于浏览器的测试时, 特别有用。

Begin by installing Selenium:

首先安装Selenium:

$ pipinstall selenium

( )

Now, let's set up Selenium. We are using , an extension that provides unit testing utilities for Flask. To use Selenium with Flask Testing, we need to inherit from the LiveServerTestCase class, which will allow us to run a live server during our tests.

现在,让我们设置Selenium。 我们正在使用 ,这是为Flask提供单元测试实用程序的扩展。 要将Selenium与Flask Testing一起使用,我们需要从LiveServerTestCase类继承,这将使我们能够在测试期间运行实时服务器。

We will use , a WebDriver for Chrome. This will allow us to run the front-end tests on the Chrome browser. If you are on MacOS, you can install it simply using the brew install chromedriver command. For other platforms, follow this guide to download and properly set up ChromeDriver. Note that you will need to have Chrome browser installed.

我们将使用 (Chrome的WebDriver)。 这将使我们能够在Chrome浏览器上运行前端测试。 如果您使用的是MacOS,则只需使用brew install chromedriver命令即可安装它。 对于其他平台,请按照此下载并正确设置ChromeDriver。 请注意,您将需要安装Chrome浏览器。

If you prefer to use Firefox broswer for your tests, you can install and set up instead.

如果您希望使用Firefox浏览器进行测试,则可以安装并设置 。

For the front-end tests, we will begin by creating test data that will be added to the database before each test runs. This includes two test users, two test departments, and two test roles. Add the following code to tests/test_front_end.py:

对于前端测试,我们将从创建测试数据开始,这些数据将在每次测试运行之前添加到数据库中。 这包括两个测试用户,两个测试部门和两个测试角色。 将以下代码添加到tests/test_front_end.py

# tests/front_end_tests.pyimport unittestimport urllib2from flask_testing import LiveServerTestCasefrom selenium import webdriverfrom app import create_app, dbfrom app.models import Employee, Role, Department# Set test variables for test admin usertest_admin_username = "admin"test_admin_email = "admin@email.com"test_admin_password = "admin2016"# Set test variables for test employee 1test_employee1_first_name = "Test"test_employee1_last_name = "Employee"test_employee1_username = "employee1"test_employee1_email = "employee1@email.com"test_employee1_password = "1test2016"# Set test variables for test employee 2test_employee2_first_name = "Test"test_employee2_last_name = "Employee"test_employee2_username = "employee2"test_employee2_email = "employee2@email.com"test_employee2_password = "2test2016"# Set variables for test department 1test_department1_name = "Human Resources"test_department1_description = "Find and keep the best talent"# Set variables for test department 2test_department2_name = "Information Technology"test_department2_description = "Manage all tech systems and processes"# Set variables for test role 1test_role1_name = "Head of Department"test_role1_description = "Lead the entire department"# Set variables for test role 2test_role2_name = "Intern"test_role2_description = "3-month learning position"class TestBase(LiveServerTestCase):    def create_app(self):        config_name = 'testing'        app = create_app(config_name)        app.config.update(            # Specify the test database            SQLALCHEMY_DATABASE_URI='mysql://dt_admin:dt2016@localhost/dreamteam_test',            # Change the port that the liveserver listens on            LIVESERVER_PORT=8943        )        return app    def setUp(self):        """Setup the test driver and create test users"""        self.driver = webdriver.Chrome()        self.driver.get(self.get_server_url())        db.session.commit()        db.drop_all()        db.create_all()        # create test admin user        self.admin = Employee(username=test_admin_username,                              email=test_admin_email,                              password=test_admin_password,                              is_admin=True)        # create test employee user        self.employee = Employee(username=test_employee1_username,                                 first_name=test_employee1_first_name,                                 last_name=test_employee1_last_name,                                 email=test_employee1_email,                                 password=test_employee1_password)        # create test department        self.department = Department(name=test_department1_name,                                     description=test_department1_description)        # create test role        self.role = Role(name=test_role1_name,                         description=test_role1_description)        # save users to database        db.session.add(self.admin)        db.session.add(self.employee)        db.session.add(self.department)        db.session.add(self.role)        db.session.commit()    def tearDown(self):        self.driver.quit()    def test_server_is_up_and_running(self):        response = urllib2.urlopen(self.get_server_url())        self.assertEqual(response.code, 200)if __name__ == '__main__':    unittest.main()

At the beginning of the file, right after the imports, we begin by creating test variables which we will use in the tests. These include details for three test users, one admin and the other two non-admin, as well as two test departments and two test roles.

在文件的开头,紧随导入之后,我们首先创建将在测试中使用的测试变量。 其中包括三个测试用户的详细信息,一个是管理员,另一个是两个非管理员,以及两个测试部门和两个测试角色。

Next, we create a TestBase class, which subsequent test classes will inherit from. In it, we have the create_app method, which Flask Testing requires to return an app instance. In this method, we specify the Flask configuration and test database, just like we did in the back-end tests. We also specify the port that the live server will run on. Flask uses port 5000 by default. Changing this for the live server will ensure that the tests run on another port (in this case, port 8943), thus preventing any conflict with another Flask app that could be running.

接下来,我们创建一个TestBase类,后续的测试类将继承该类。 在其中,我们具有create_app方法,Flask Testing要求该方法返回一个应用程序实例。 在此方法中,我们指定Flask配置和测试数据库,就像在后端测试中所做的一样。 我们还指定运行实时服务器的端口。 Flask默认使用端口5000。 为实时服务器更改此设置将确保测试在另一个端口(在本例中为8943端口)上运行,从而防止与可能正在运行的另一个Flask应用程序发生任何冲突。

In the setUp method, we set up the test driver as the ChromeDriver. (If using another webdriver, such as Firefox for example, use self.driver = webdriver.Firefox().) Take note of the self.get_server_url method; we use this to get the home URL. We also create a test employee, test admin user, test department and test role using the variables we created initially. In the tearDown method, we quit the webdriver, and clear the test database. The setUp method is called before every test, while the tearDown method is called after each test. It is important to have the test data before the tests run because most of our tests will require existing users, departments and roles. It would be repetitive to create new users, departments and roles for each test, so it is simpler and DRY-er to do it in the setUp method.

setUp方法中,我们将测试驱动程序设置为ChromeDriver。 (例如,如果使用其他webdriver,例如Firefox,请使用self.driver = webdriver.Firefox() 。)注意self.get_server_url方法; 我们用它来获得家庭URL。 我们还使用最初创建的变量来创建测试员工,测试管理员用户,测试部门和测试角色。 在tearDown方法中,我们退出Web驱动程序,并清除测试数据库。 在每次测试之前都会调用setUp方法,而在每次测试之后都会调用tearDown方法。 在运行测试之前拥有测试数据非常重要,因为我们的大多数测试都将需要现有的用户,部门和角色。 为每个测试创建新的用户,部门和角色将是重复的,因此在setUp方法中进行此操作更简单且更setUp

We have one test method in the base class, test_server_is_up_and_running. This method simply ensures that the test server is working as expected and returns a 200 OK status code.

在基类中,我们有一个测试方法test_server_is_up_and_running 。 此方法仅确保测试服务器按预期工作,并返回200 OK状态代码。

Run the tests using the nose2 command:

使用nose2命令运行测试:

$ nose2...............----------------------------------------------------------------------Ran 15 tests in 4.313sOK

It shows that it ran 15 tests, which includes the 14 back-end tests that we had initially, as well as 1 front-end test.

它表明它运行了15个测试,其中包括我们最初拥有的14个后端测试以及1个前端测试。

( )

So far, we have only one test in the test_front_end.py file. We need to write additional tests that simulate a user actually using the app, such as clicking links and buttons, entering data into forms, and so on. To tell Selenium to click a particular button or enter data in a particular field in a form, we need to use unique identifiers for these web elements.

到目前为止, test_front_end.py文件中只有一个测试。 我们需要编写其他测试来模拟实际使用该应用程序的用户,例如单击链接和按钮,将数据输入表单等。 要告诉Selenium单击特定按钮或在表单的特定字段中输入数据,我们需要为这些Web元素使用唯一的标识符。

Selenium provides methods that we can call to find web elements in a variety of ways, such as by , by , by , and by . I find that locating an element by id is one of the simplest ways; it only requires you to give an id attribute to the element that needs to be located.

Selenium提供了一些方法,我们可以通过各种方法来调用这些方法来查找Web元素,例如通过 ,通过 ,通过和通过 。 我发现按id定位元素是最简单的方法之一。 它只需要您为需要定位的元素赋予id属性。

We'll start by assigning id attributes to the menu links in the app so that we can direct the test user to click them as required. Open the templates/base.html file and update the menu items with id attributes as follows:

我们将从为应用程序中的菜单链接分配id属性开始,以便我们可以指导测试用户根据需要单击它们。 打开templates/base.html文件,并更新具有id属性的菜单项,如下所示:

Most other web elements that we will need to locate already have id attributes. For those that don't, we will find them by their CSS class names and selectors.

我们将需要定位的大多数其他Web元素已经具有id属性。 对于那些没有的人,我们将通过它们CSS类名称和选择器找到它们。

( )

We will begin by writing tests for the Auth blueprint, which handles registration, login, and logout. These tests need to handle edge cases, such as users entering invalid data, so that we can ensure the app responds appropriately.

我们将从为Auth蓝图编写测试开始,该蓝图处理注册,登录和注销。 这些测试需要处理极端情况,例如用户输入无效数据,以便我们确保应用正确响应。

注册测试 (Registration Tests)

We'll start by writing the registration tests. Add the following code to the test_front_end.py file, after the TestBase class:

我们将从编写注册测试开始。 在TestBase类之后,将以下代码添加到test_front_end.py文件中:

# tests/front_end_tests.py# update importsimport timefrom flask import url_forclass TestRegistration(TestBase):    def test_registration(self):        """        Test that a user can create an account using the registration form        if all fields are filled out correctly, and that they will be         redirected to the login page        """        # Click register menu link        self.driver.find_element_by_id("register_link").click()        time.sleep(1)        # Fill in registration form        self.driver.find_element_by_id("email").send_keys(test_employee2_email)        self.driver.find_element_by_id("username").send_keys(            test_employee2_username)        self.driver.find_element_by_id("first_name").send_keys(            test_employee2_first_name)        self.driver.find_element_by_id("last_name").send_keys(            test_employee2_last_name)        self.driver.find_element_by_id("password").send_keys(            test_employee2_password)        self.driver.find_element_by_id("confirm_password").send_keys(            test_employee2_password)        self.driver.find_element_by_id("submit").click()        time.sleep(1)        # Assert that browser redirects to login page        assert url_for('auth.login') in self.driver.current_url        # Assert success message is shown        success_message = self.driver.find_element_by_class_name("alert").text        assert "You have successfully registered" in success_message        # Assert that there are now 3 employees in the database        self.assertEqual(Employee.query.count(), 3)    def test_registration_invalid_email(self):        """        Test that a user cannot register using an invalid email format        and that an appropriate error message will be displayed        """        # Click register menu link        self.driver.find_element_by_id("register_link").click()        time.sleep(1)        # Fill in registration form        self.driver.find_element_by_id("email").send_keys("invalid_email")        self.driver.find_element_by_id("username").send_keys(            test_employee2_username)        self.driver.find_element_by_id("first_name").send_keys(            test_employee2_first_name)        self.driver.find_element_by_id("last_name").send_keys(            test_employee2_last_name)        self.driver.find_element_by_id("password").send_keys(            test_employee2_password)        self.driver.find_element_by_id("confirm_password").send_keys(            test_employee2_password)        self.driver.find_element_by_id("submit").click()        time.sleep(5)        # Assert error message is shown        error_message = self.driver.find_element_by_class_name(            "help-block").text        assert "Invalid email address" in error_message    def test_registration_confirm_password(self):        """        Test that an appropriate error message is displayed when the password         and confirm_password fields do not match        """        # Click register menu link        self.driver.find_element_by_id("register_link").click()        time.sleep(1)        # Fill in registration form        self.driver.find_element_by_id("email").send_keys(test_employee2_email)        self.driver.find_element_by_id("username").send_keys(            test_employee2_username)        self.driver.find_element_by_id("first_name").send_keys(            test_employee2_first_name)        self.driver.find_element_by_id("last_name").send_keys(            test_employee2_last_name)        self.driver.find_element_by_id("password").send_keys(            test_employee2_password)        self.driver.find_element_by_id("confirm_password").send_keys(            "password-won't-match")        self.driver.find_element_by_id("submit").click()        time.sleep(5)        # Assert error message is shown        error_message = self.driver.find_element_by_class_name(            "help-block").text        assert "Field must be equal to confirm_password" in error_message

We've created a class, TestRegistration, that inherits from the TestBase class, and has three test methods. Note that the if __name__ == '__main__': section in the file should always be at the bottom of the file, so any new code additions will go before it.

我们创建了一个类TestRegistration ,该类继承自TestBase类,并且具有三个测试方法。 请注意,文件中的if __name__ == '__main__':部分应始终位于文件的底部,因此所有新添加的代码都将位于该文件的底部。

The test_registration method tests that a user can successfully create an account if they fill out all fields in the registration form correctly. It also tests that the user is redirected to the login page after registration and that a message is displayed inviting them to login. The test_registration_invalid_email method tests that entering an invalid email format in the email field will prevent the registration form from being submitted, and display an appropriate error message. The test_registration_confirm_password method tests that the data in the "Password" and "Confirm password" fields must match, and that an appropriate error message is displayed if they do not.

如果用户正确填写了注册表单中的所有字段,则test_registration方法测试用户是否可以成功创建帐户。 它还测试用户注册后是否将用户重定向到登录页面,并显示一条消息邀请他们登录。 test_registration_invalid_email方法测试在电子邮件字段中输入无效的电子邮件格式将阻止提交注册表,并显示适当的错误消息。 test_registration_confirm_password方法测试“密码”和“确认密码”字段中的数据必须匹配,如果不匹配,则会显示相应的错误消息。

Take note of the send_keys method, which we use to enter data into form fields, and the click method, which we use to click on web elements such as buttons and links. Additionally, take note of the time.sleep method. We use this to pause the tests to give the browser time to load all web elements completely before proceeding to the next step.

请注意send_keys方法(用于在表单字段中输入数据)和click方法(用于单击按钮和链接等Web元素)。 此外,请注意time.sleep方法。 我们使用它来暂停测试,以便浏览器有时间完全加载所有Web元素,然后再继续下一步。

登录测试 (Login Tests)

The next tests are for logging in. Add a TestLogin class as follows:

下一个测试是用于登录的。添加TestLogin类,如下所示:

# tests/front_end_tests.pyclass TestLogin(TestBase):    def test_login(self):        """        Test that a user can login and that they will be redirected to        the homepage        """        # Click login menu link        self.driver.find_element_by_id("login_link").click()        time.sleep(1)        # Fill in login form        self.driver.find_element_by_id("email").send_keys(test_employee1_email)        self.driver.find_element_by_id("password").send_keys(            test_employee1_password)        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert that browser redirects to dashboard page        assert url_for('home.dashboard') in self.driver.current_url        # Assert that welcome greeting is shown        username_greeting = self.driver.find_element_by_id(            "username_greeting").text        assert "Hi, employee1!" in username_greeting    def test_admin_login(self):        """        Test that an admin user can login and that they will be redirected to        the admin homepage        """        # Click login menu link        self.driver.find_element_by_id("login_link").click()        time.sleep(1)        # Fill in login form        self.driver.find_element_by_id("email").send_keys(test_admin_email)        self.driver.find_element_by_id("password").send_keys(            test_admin_password)        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert that browser redirects to dashboard page        assert url_for('home.admin_dashboard') in self.driver.current_url        # Assert that welcome greeting is shown        username_greeting = self.driver.find_element_by_id(            "username_greeting").text        assert "Hi, admin!" in username_greeting    def test_login_invalid_email_format(self):        """        Test that a user cannot login using an invalid email format        and that an appropriate error message will be displayed        """        # Click login menu link        self.driver.find_element_by_id("login_link").click()        time.sleep(1)        # Fill in login form        self.driver.find_element_by_id("email").send_keys("invalid")        self.driver.find_element_by_id("password").send_keys(            test_employee1_password)        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert error message is shown        error_message = self.driver.find_element_by_class_name(            "help-block").text        assert "Invalid email address" in error_message    def test_login_wrong_email(self):        """        Test that a user cannot login using the wrong email        and that an appropriate error message will be displayed        """        # Click login menu link        self.driver.find_element_by_id("login_link").click()        time.sleep(1)        # Fill in login form        self.driver.find_element_by_id("email").send_keys(test_employee2_email)        self.driver.find_element_by_id("password").send_keys(            test_employee1_password)        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert that error message is shown        error_message = self.driver.find_element_by_class_name("alert").text        assert "Invalid email or password" in error_message    def test_login_wrong_password(self):        """        Test that a user cannot login using the wrong password        and that an appropriate error message will be displayed        """        # Click login menu link        self.driver.find_element_by_id("login_link").click()        time.sleep(1)        # Fill in login form        self.driver.find_element_by_id("email").send_keys(test_employee1_email)        self.driver.find_element_by_id("password").send_keys(            "invalid")        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert that error message is shown        error_message = self.driver.find_element_by_class_name("alert").text        assert "Invalid email or password" in error_message

The test_login method tests that when the correct email and password combination is entered for a registered user, the user is redirected to the dashboard, where their username is displayed. It is the same as the test_admin_login method, except the latter tests login for admin users who are redirected to the admin dashboard. The test_invalid_email_format tests that users cannot submit the login form if they enter a non-email in the email field. The test_login_wrong_email and test_login_wrong_password methods test that a user cannot login using invalid credentials, and that an appropriate error message is displayed.

test_login方法测试是否为注册用户输入了正确的电子邮件和密码组合,然后将该用户重定向到显示其用户名的仪表板。 它与test_admin_login方法相同,除了后者测试重定向到管理仪表板的管理员用户的登录名。 test_invalid_email_format测试用户是否在电子邮件字段中输入非电子邮件,则不能提交登录表单。 test_login_wrong_emailtest_login_wrong_password方法测试用户无法使用无效的凭据登录,并显示相应的错误消息。

Run the tests again. You'll notice that for the front-end tests, a browser window will open. It will simulate a user clicking links and filling out forms as specified in the tests we wrote. The output of running the tests should be similar to this:

再次运行测试。 您会注意到,对于前端测试,将打开一个浏览器窗口。 它将模拟用户单击链接并按照我们编写的测试中指定的方式填写表格。 运行测试的输出应与此类似:

$ nose2........................----------------------------------------------------------------------Ran 25 tests in 96.581sOK

( )

Next, we will write tests for the Admin blueprint, which is where the CRUD functionality of the application is. In these tests, we will simulate an admin user creating, viewing, updating, and deleting departments and roles. We will also simulate an admin user assigning departments and roles to employees.

接下来,我们将为Admin蓝图编写测试,这是应用程序的CRUD功能所在的位置。 在这些测试中,我们将模拟管理员用户创建,查看,更新和删除部门和角色。 我们还将模拟一个为用户分配部门和角色的管理员用户。

Because we will need to log in multiple times before being able to do any of this, we will create a method that we will call every time we need to log in as a user.

因为我们需要多次登录才能执行任何操作,所以我们将创建一个方法,每次需要以用户身份登录时都会调用该方法。

In your front_end_tests.py file, add a CreateObjects class, just after the test variables and before the TestRegistration class:

在您的front_end_tests.py文件中,在测试变量之后和TestRegistration类之前添加一个CreateObjects类:

# tests/front_end_tests.py# after initialization of test variablesclass CreateObjects(object):    def login_admin_user(self):        """Log in as the test employee"""        login_link = self.get_server_url() + url_for('auth.login')        self.driver.get(login_link)        self.driver.find_element_by_id("email").send_keys(test_admin_email)        self.driver.find_element_by_id("password").send_keys(            test_admin_password)        self.driver.find_element_by_id("submit").click()    def login_test_user(self):        """Log in as the test employee"""        login_link = self.get_server_url() + url_for('auth.login')        self.driver.get(login_link)        self.driver.find_element_by_id("email").send_keys(test_employee1_email)        self.driver.find_element_by_id("password").send_keys(            test_employee1_password)        self.driver.find_element_by_id("submit").click()

Now, if we need to be logged in as a particular type of user in any of our tests, we can simply call the relevant method.

现在,如果我们需要在任何测试中以特定类型的用户身份登录,则只需调用相关方法即可。

部门测试 (Department Tests)

Next, we'll write our tests. Add a TestDepartments class after the TestLogin class, as follows:

接下来,我们将编写测试。 添加TestDepartments类后TestLogin类,如下所示:

# tests/front_end_tests.pyclass TestDepartments(CreateObjects, TestBase):    def test_add_department(self):        """        Test that an admin user can add a department        """        # Login as admin user        self.login_admin_user()        # Click departments menu link        self.driver.find_element_by_id("departments_link").click()        time.sleep(1)        # Click on add department button        self.driver.find_element_by_class_name("btn").click()        time.sleep(1)        # Fill in add department form        self.driver.find_element_by_id("name").send_keys(test_department2_name)        self.driver.find_element_by_id("description").send_keys(            test_department2_description)        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert success message is shown        success_message = self.driver.find_element_by_class_name("alert").text        assert "You have successfully added a new department" in success_message        # Assert that there are now 2 departments in the database        self.assertEqual(Department.query.count(), 2)    def test_add_existing_department(self):        """        Test that an admin user cannot add a department with a name        that already exists        """        # Login as admin user        self.login_admin_user()        # Click departments menu link        self.driver.find_element_by_id("departments_link").click()        time.sleep(1)        # Click on add department button        self.driver.find_element_by_class_name("btn").click()        time.sleep(1)        # Fill in add department form        self.driver.find_element_by_id("name").send_keys(test_department1_name)        self.driver.find_element_by_id("description").send_keys(            test_department1_description)        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert error message is shown        error_message = self.driver.find_element_by_class_name("alert").text        assert "Error: department name already exists" in error_message        # Assert that there is still only 1 department in the database        self.assertEqual(Department.query.count(), 1)    def test_edit_department(self):        """        Test that an admin user can edit a department        """        # Login as admin user        self.login_admin_user()        # Click departments menu link        self.driver.find_element_by_id("departments_link").click()        time.sleep(1)        # Click on edit department link        self.driver.find_element_by_class_name("fa-pencil").click()        time.sleep(1)        # Fill in add department form        self.driver.find_element_by_id("name").clear()        self.driver.find_element_by_id("name").send_keys("Edited name")        self.driver.find_element_by_id("description").clear()        self.driver.find_element_by_id("description").send_keys(            "Edited description")        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert success message is shown        success_message = self.driver.find_element_by_class_name("alert").text        assert "You have successfully edited the department" in success_message        # Assert that department name and description has changed        department = Department.query.get(1)        self.assertEqual(department.name, "Edited name")        self.assertEqual(department.description, "Edited description")    def test_delete_department(self):        """        Test that an admin user can delete a department        """        # Login as admin user        self.login_admin_user()        # Click departments menu link        self.driver.find_element_by_id("departments_link").click()        time.sleep(1)        # Click on edit department link        self.driver.find_element_by_class_name("fa-trash").click()        time.sleep(1)        # Assert success message is shown        success_message = self.driver.find_element_by_class_name("alert").text        assert "You have successfully deleted the department" in success_message        # Assert that there are no departments in the database        self.assertEqual(Department.query.count(), 0)

The test_add_department method tests that an admin user can add a department using the add department form, while the test_add_existing_department method tests that they cannot add a department name that already exists. The test_edit_department method tests that a department can be edited using the edit form. Take note of the clear method, which allows us to clear a textbox of any exisiting text before entering some other text. Lastly, the test_delete_department tests than a department can be deleted using the delete link in the departments page.

test_add_department方法测试管理员用户可以使用添加部门表单来添加部门,而test_add_existing_department方法测试他们不能添加已经存在的部门名称。 test_edit_department方法测试可以使用编辑表单来编辑部门。 注意clear方法,该方法使我们可以在输入其他文本之前清除所有现有文本的文本框。 最后,可以使用“部门”页面中的“删除”链接来删除一个部门的test_delete_department测试。

角色测试 (Role Tests)

We will write similar tests for roles:

我们将为角色编写类似的测试:

# tests/front_end_tests.pyclass TestRoles(CreateObjects, TestBase):    def test_add_role(self):        """        Test that an admin user can add a role        """        # Login as admin user        self.login_admin_user()        # Click roles menu link        self.driver.find_element_by_id("roles_link").click()        time.sleep(1)        # Click on add role button        self.driver.find_element_by_class_name("btn").click()        time.sleep(1)        # Fill in add role form        self.driver.find_element_by_id("name").send_keys(test_role2_name)        self.driver.find_element_by_id("description").send_keys(            test_role2_description)        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert success message is shown        success_message = self.driver.find_element_by_class_name("alert").text        assert "You have successfully added a new role" in success_message        # Assert that there are now 2 roles in the database        self.assertEqual(Role.query.count(), 2)    def test_add_existing_role(self):        """        Test that an admin user cannot add a role with a name        that already exists        """        # Login as admin user        self.login_admin_user()        # Click roles menu link        self.driver.find_element_by_id("roles_link").click()        time.sleep(1)        # Click on add role button        self.driver.find_element_by_class_name("btn").click()        time.sleep(1)        # Fill in add role form        self.driver.find_element_by_id("name").send_keys(test_role1_name)        self.driver.find_element_by_id("description").send_keys(            test_role1_description)        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert error message is shown        error_message = self.driver.find_element_by_class_name("alert").text        assert "Error: role name already exists" in error_message        # Assert that there is still only 1 role in the database        self.assertEqual(Role.query.count(), 1)    def test_edit_role(self):        """        Test that an admin user can edit a role        """        # Login as admin user        self.login_admin_user()        # Click roles menu link        self.driver.find_element_by_id("roles_link").click()        time.sleep(1)        # Click on edit role link        self.driver.find_element_by_class_name("fa-pencil").click()        time.sleep(1)        # Fill in add role form        self.driver.find_element_by_id("name").clear()        self.driver.find_element_by_id("name").send_keys("Edited name")        self.driver.find_element_by_id("description").clear()        self.driver.find_element_by_id("description").send_keys(            "Edited description")        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert success message is shown        success_message = self.driver.find_element_by_class_name("alert").text        assert "You have successfully edited the role" in success_message        # Assert that role name and description has changed        role = Role.query.get(1)        self.assertEqual(role.name, "Edited name")        self.assertEqual(role.description, "Edited description")    def test_delete_role(self):        """        Test that an admin user can delete a role        """        # Login as admin user        self.login_admin_user()        # Click roles menu link        self.driver.find_element_by_id("roles_link").click()        time.sleep(1)        # Click on edit role link        self.driver.find_element_by_class_name("fa-trash").click()        time.sleep(1)        # Assert success message is shown        success_message = self.driver.find_element_by_class_name("alert").text        assert "You have successfully deleted the role" in success_message        # Assert that there are no roles in the database        self.assertEqual(Role.query.count(), 0)

异常处理 (Exception Handling)

You may also need to edit the add_department and add_role views to handle an SQLAlchemy exception that may occur during transaction rollback when attempting to add a department or role that already exists. To handle this exception, edit the try-except block in the add_department and add_role views, as follows:

您可能还需要编辑add_departmentadd_role视图,以处理在尝试添加已存在的部门或角色时在事务回滚期间可能发生SQLAlchemy异常。 要处理此异常,请在add_departmentadd_role视图中编辑try-except块,如下所示:

# app/admin/views.pydef add_department(self):    # existing code remains    if form.validate_on_submit():        department = Department(name=form.name.data,                                description=form.description.data)        try:            # add department to the database            db.session.add(department)            db.session.commit()            flash('You have successfully added a new department.')        except:            # in case department name already exists            db.session.rollback()            flash('Error: department name already exists.')def add_role(self):    # existing code remains   if form.validate_on_submit():        role = Role(name=form.name.data,                    description=form.description.data)        try:            # add role to the database            db.session.add(role)            db.session.commit()            flash('You have successfully added a new role.')        except:            # in case role name already exists            db.session.rollback()            flash('Error: role name already exists.')

Run your tests now:

立即运行测试:

$ nose2...................................----------------------------------------------------------------------Ran 35 tests in 234.457sOK

员工测试 (Employee Tests)

Now we will write tests where the admin user assigns departments and roles to employees.

现在,我们将编写测试,其中管理员用户将部门和角色分配给员工。

# tests/front_end_tests.py# update importsfrom selenium.webdriver.support.ui import Selectclass TestEmployees(CreateObjects, TestBase):    def test_assign(self):        """        Test that an admin user can assign a role and a department        to an employee        """        # Login as admin user        self.login_admin_user()        # Click employees menu link        self.driver.find_element_by_id("employees_link").click()        time.sleep(1)        # Click on assign link        self.driver.find_element_by_class_name("fa-user-plus").click()        time.sleep(1)        # Department and role already loaded in form        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert success message is shown        success_message = self.driver.find_element_by_class_name("alert").text        assert "You have successfully assigned a department and role" in success_message        # Assert that department and role has been assigned to employee        employee = Employee.query.get(2)        self.assertEqual(employee.role.name, test_role1_name)        self.assertEqual(employee.department.name, test_department1_name)    def test_reassign(self):        """        Test that an admin user can assign a new role and a new department        to an employee        """        # Create new department        department = Department(name=test_department2_name,                                description=test_department2_description)        # Create new role        role = Role(name=test_role2_name,                    description=test_role2_description)        # Add to database        db.session.add(department)        db.session.add(role)        db.session.commit()        # Login as admin user        self.login_admin_user()        # Click employees menu link        self.driver.find_element_by_id("employees_link").click()        time.sleep(1)        # Click on assign link        self.driver.find_element_by_class_name("fa-user-plus").click()        time.sleep(1)        # Select new department and role        select_dept = Select(self.driver.find_element_by_id("department"))        select_dept.select_by_visible_text(test_department2_name)        select_role = Select(self.driver.find_element_by_id("role"))        select_role.select_by_visible_text(test_role2_name)        self.driver.find_element_by_id("submit").click()        time.sleep(2)        # Assert success message is shown        success_message = self.driver.find_element_by_class_name("alert").text        assert "You have successfully assigned a department and role" in success_message        # Assert that employee's department and role has changed        employee = Employee.query.get(2)        self.assertEqual(employee.role.name, test_role2_name)        self.assertEqual(employee.department.name, test_department2_name)

The test_assign method assigns the exisiting department and role to the existing user using the assign link in the Employees page. The test_reassign method adds a new department and role to the database, and then assigns them to the existing employee. Take note of the Select class that we have imported from Selenium. From it, we use the select_by_visible_text method to select the department and role from a dropdown menu.

使用雇员页面中的分配链接, test_assign方法将现有部门和角色分配给现有用户。 test_reassign方法将新部门和角色添加到数据库,然后将它们分配给现有员工。 请注意我们从Selenium导入的Select类。 从中,我们使用select_by_visible_text方法从下拉菜单中选择部门和角色。

Let's run the tests one more time:

让我们再运行一​​次测试:

$ nose2......................................----------------------------------------------------------------------Ran 38 tests in 181.709sOK

( )

That's it for Part One! To recap, in this tutorial you've learnt about Selenium WebDriver and how it can be used to run front-end tests by simulating a user of your app. You've also learnt about web elements and how to find them using some of Selenium's in-built methods. We have written tests for registration, login, and performing CRUD operations on departments and roles.

就是第一部分了! 回顾一下,在本教程中,您了解了Selenium WebDriver,以及如何通过模拟应用程序的用户将其用于运行前端测试。 您还了解了Web元素以及如何使用Selenium的某些内置方法找到它们。 我们已经针对部门和角色进行了注册,登录和执行CRUD操作的书面测试。

In Part Two, we will write tests for permissions, to ensure that only authorised users can access certain resources. Part Two will also cover continuous integration and linking our app with , a continuous integration and delivery platform.

在第二部分中,我们将编写权限测试,以确保只有授权用户才能访问某些资源。 第二部分还将介绍持续集成以及将我们的应用程序与 (一个持续集成和交付平台)链接。

翻译自:

转载地址:http://zruwd.baihongyu.com/

你可能感兴趣的文章
Selenium自动化-调用Mysql数据库
查看>>
项目一
查看>>
[转载]AAF灵便应用框架简介系列(6):休息一下,泛谈面向对象 Why OO+多层结构?...
查看>>
android EditView ime
查看>>
javascript 学习随笔7
查看>>
<P>标签小细节
查看>>
Linux 命令 - netstat
查看>>
mac 关闭&&显示隐藏文件命令
查看>>
JavaScript 循环绑定之变量污染
查看>>
poj 1038 Bugs Integrated, Inc. 三进制状态压缩 DFS 滚动数组
查看>>
zoj 1654 Place the Rebots 最大独立集转换成二分图最大独立边(最大匹配)
查看>>
Wordpress解析系列之PHP编写hook钩子原理简单实例
查看>>
怎样看待个体经济
查看>>
不明觉厉的数据结构题2
查看>>
面向对象编程思想概览(四)多线程
查看>>
二十三种设计模式及其python实现
查看>>
Math类、Random类、System类、BigInteger类、BigDecimal类、Date类、SimpleDateFormat、Calendar类...
查看>>
【设计模式】 访问者模式
查看>>
关于FFMPEG 中I帧、B帧、P帧、PTS、DTS
查看>>
request和response的知识
查看>>